namespace OneWorks.Utils.FluentData
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using System.Text;

    internal partial class DbCommand
    {
        public T ExecuteReturnLastId<T>(string identityColumnName = null)
        {
            if (this.Data.Context.Data.Provider.RequiresIdentityColumn && string.IsNullOrEmpty(identityColumnName))
                throw new FluentDataException("The identity column must be given");

            var value = this.Data.Context.Data.Provider.ExecuteReturnLastId<T>(this, identityColumnName);
            T lastId;

            if (value.GetType() == typeof(T))
                lastId = (T)value;
            else
                lastId = (T)Convert.ChangeType(value, typeof(T));

            return lastId;
        }
    }

    internal partial class DbCommand
    {
        public TEntity QuerySingle<TEntity>(Action<TEntity, IDataReader> customMapper)
        {
            var item = default(TEntity);

            this.Data.ExecuteQueryHandler.ExecuteQuery(true, () =>
                                                            {
                                                                item = new QueryHandler<TEntity>(this.Data).ExecuteSingle(customMapper, null);
                                                            });

            return item;
        }

        public TEntity QuerySingle<TEntity>(Action<TEntity, dynamic> customMapper)
        {
            var item = default(TEntity);

            this.Data.ExecuteQueryHandler.ExecuteQuery(true, () =>
                                                            {
                                                                item = new QueryHandler<TEntity>(this.Data).ExecuteSingle(customMapper, null);
                                                            });

            return item;
        }
    }

    internal partial class DbCommand
    {
        public TEntity QueryComplexSingle<TEntity>(Func<IDataReader, TEntity> customMapper)
        {
            var item = default(TEntity);

            this.Data.ExecuteQueryHandler.ExecuteQuery(true, () =>
                                                            {
                                                                if (this.Data.Reader.Read())
                                                                    item = customMapper(this.Data.Reader);
                                                            });

            return item;
        }

        public TEntity QueryComplexSingle<TEntity>(Func<dynamic, TEntity> customMapper)
        {
            var item = default(TEntity);

            this.Data.ExecuteQueryHandler.ExecuteQuery(true, () =>
                                                            {
                                                                if (this.Data.Reader.Read())
                                                                    item = customMapper(new DynamicDataReader(this.Data.Reader));
                                                            });

            return item;
        }
    }

    internal partial class DbCommand
    {
        public void QueryComplexMany<TEntity>(IList<TEntity> list, Action<IList<TEntity>, IDataReader> customMapper)
        {
            this.Data.ExecuteQueryHandler.ExecuteQuery(true, () =>
                                                            {
                                                                while(this.Data.Reader.Read())
                                                                    customMapper(list, this.Data.Reader);
                                                            });
        }

        public void QueryComplexMany<TEntity>(IList<TEntity> list, Action<IList<TEntity>, dynamic> customMapper)
        {
            this.Data.ExecuteQueryHandler.ExecuteQuery(true, () =>
                                                            {
                                                                while (this.Data.Reader.Read())
                                                                    customMapper(list, this.Data.Reader);
                                                            });
        }
    }

    internal partial class DbCommand
    {
        public IDbCommand Sql(string sql)
        {
            this.Data.Sql.Append(sql);
            return this;
        }

        public IDbCommand ClearSql
        {
            get
            {
                this.Data.Sql.Clear();
                return this;
            }
        }
    }

    internal partial class DbCommand
    {
        public int Execute()
        {
            var recordsAffected = 0;

            this.Data.ExecuteQueryHandler.ExecuteQuery(false, () =>
                                                             {
                                                                 recordsAffected = this.Data.InnerCommand.ExecuteNonQuery();
                                                             });
            return recordsAffected;
        }
    }

    internal partial class DbCommand
    {
        public IDbCommand Parameter(string name, object value, DataTypes parameterType = DataTypes.Object, ParameterDirection direction = ParameterDirection.Input, int size = 0)
        {
            if (ReflectionHelper.IsList(value))
                this.AddListParameterToInnerCommand(name, value);
            else
                this.AddParameterToInnerCommand(name, value, parameterType, direction, size);

            return this;
        }

        private int _currentIndex = 0;
        public IDbCommand Parameters(params object[] parameters)
        {
            if(parameters != null)
            {
                for (var i = 0; i < parameters.Count(); i++)
                {
                    this.Parameter(this._currentIndex.ToString(), parameters[this._currentIndex]);
                    this._currentIndex++;
                }
            }
            return this;
        }

        private void AddListParameterToInnerCommand(string name, object value)
        {
            var list = (IEnumerable) value;

            var newInStatement = new StringBuilder();

            var k = -1;
            foreach (var item in list)
            {
                k++;
                if (k == 0)
                    newInStatement.Append(" in(");
                else
                    newInStatement.Append(",");
				
                var parameter = this.AddParameterToInnerCommand("p" + name + "p" + k.ToString(), item);

                newInStatement.Append(parameter.ParameterName);
            }
            newInStatement.Append(")");

            var oldInStatement = string.Format(" in({0})", this.Data.Context.Data.Provider.GetParameterName(name));
            this.Data.Sql.Replace(oldInStatement, newInStatement.ToString());
        }

        private IDbDataParameter AddParameterToInnerCommand(string name, object value, DataTypes parameterType = DataTypes.Object, ParameterDirection direction = ParameterDirection.Input, int size = 0)
        {
            if (value == null)
                value = DBNull.Value;

            if (value.GetType().IsEnum)
                value = (int) value;

            var dbParameter = this.Data.InnerCommand.CreateParameter();
            if (parameterType == DataTypes.Object)
                dbParameter.DbType = (System.Data.DbType) this.Data.Context.Data.Provider.GetDbTypeForClrType(value.GetType());
            else
                dbParameter.DbType = (System.Data.DbType) parameterType;

            dbParameter.ParameterName = this.Data.Context.Data.Provider.GetParameterName(name);
            dbParameter.Direction = (System.Data.ParameterDirection) direction;
            dbParameter.Value = value;
            if (size > 0)
                dbParameter.Size = size;
            this.Data.InnerCommand.Parameters.Add(dbParameter);

            return dbParameter;
        }

        public IDbCommand ParameterOut(string name, DataTypes parameterType, int size)
        {
            if (!this.Data.Context.Data.Provider.SupportsOutputParameters)
                throw new FluentDataException("The selected database does not support output parameters");
            this.Parameter(name, null, parameterType, ParameterDirection.Output, size);
            return this;
        }

        public TParameterType ParameterValue<TParameterType>(string outputParameterName)
        {
            outputParameterName = this.Data.Context.Data.Provider.GetParameterName(outputParameterName);
            if (!this.Data.InnerCommand.Parameters.Contains(outputParameterName))
                throw new FluentDataException(string.Format("Parameter {0} not found", outputParameterName));

            var value = (this.Data.InnerCommand.Parameters[outputParameterName] as System.Data.IDataParameter).Value;

            if (value == DBNull.Value)
                return default(TParameterType);

            return (TParameterType)value;
        }
    }

    internal partial class DbCommand
    {
        public TList QueryMany<TEntity, TList>(Action<TEntity, IDataReader> customMapper = null)
            where TList : IList<TEntity>
        {
            var items = default(TList);

            this.Data.ExecuteQueryHandler.ExecuteQuery(true, () =>
                                                            {
                                                                items = new QueryHandler<TEntity>(this.Data).ExecuteMany<TList>(customMapper, null);
                                                            });

            return items;
        }

        public TList QueryMany<TEntity, TList>(Action<TEntity, dynamic> customMapper) where TList : IList<TEntity>
        {
            var items = default(TList);

            this.Data.ExecuteQueryHandler.ExecuteQuery(true, () =>
                                                            {
                                                                items = new QueryHandler<TEntity>(this.Data).ExecuteMany<TList>(null, customMapper);
                                                            });

            return items;
        }

        public List<TEntity> QueryMany<TEntity>(Action<TEntity, IDataReader> customMapper)
        {
            return this.QueryMany<TEntity, List<TEntity>>(customMapper);
        }

        public List<TEntity> QueryMany<TEntity>(Action<TEntity, dynamic> customMapper)
        {
            return this.QueryMany<TEntity, List<TEntity>>(customMapper);
        }

        public DataTable QueryManyDataTable()
        {
            var dataTable = new DataTable();

            this.Data.ExecuteQueryHandler.ExecuteQuery(true, () => dataTable.Load(this.Data.Reader.InnerReader, LoadOption.OverwriteChanges));

            return dataTable;
        }
    }

    internal partial class DbCommand : IDbCommand
    {
        public DbCommandData Data { get; private set; }

        public DbCommand(
            DbContext dbContext,
            System.Data.IDbCommand innerCommand)
        {
            this.Data = new DbCommandData(dbContext, innerCommand);
            this.Data.ExecuteQueryHandler = new ExecuteQueryHandler(this);
        }

        public IDbCommand UseMultiResult(bool useMultipleResultset)
        {
            if (useMultipleResultset && !this.Data.Context.Data.Provider.SupportsMultipleResultsets)
                throw new FluentDataException("The selected database does not support multiple resultset");

            this.Data.UseMultipleResultsets = useMultipleResultset;
            return this;
        }

        public IDbCommand CommandType(DbCommandTypes dbCommandType)
        {
            this.Data.InnerCommand.CommandType = (System.Data.CommandType) dbCommandType;
            return this;
        }

        internal void ClosePrivateConnection()
        {
            if (!this.Data.Context.Data.UseTransaction
                && !this.Data.Context.Data.UseSharedConnection)
            {
                this.Data.InnerCommand.Connection.Close();

                if (this.Data.Context.Data.OnConnectionClosed != null)
                    this.Data.Context.Data.OnConnectionClosed(new ConnectionEventArgs(this.Data.InnerCommand.Connection));
            }
        }

        public void Dispose()
        {
            if (this.Data.Reader != null)
                this.Data.Reader.Close();

            this.ClosePrivateConnection();
        }
    }
}